跳到主要内容

LDAP 目录服务

全文大量参考自 eftales - openldap教程

LDAP 是什么?

参考资料 LDAP概念和原理介绍

LDAP 是轻量目录访问协议(Lightweight Directory Access Protocol)的缩写

LDAP 目录服务是由目录数据库和一套访问协议组成的系统,它是一套开放的 internet标准,支持跨平台的 internet的协议。

LDAP:轻量级目录访问协议,LDAP仅仅是一个访问协议。

应用:ldap用来构建同一的账号管理、身份验证平台,实现sso单点登录机制。

LDAP 的主要特点

  • 基于 TCP/IP
  • 以树状结构存储数据
  • 读取速度快,写入速度慢
  • 采用 client-server模型,服务器用于存放数据,客户端用于操作数据
  • 跨平台、维护简单
  • 支持 SSL/TLS 加密
  • 协议是开放的

LDAP 的主要产品

LDAP的中文全称是:轻量级目录访问协议,说到底 LDAP仅仅是一个访问协议,那么我们的数据究竟存储在哪里呢?

上面的工具就是正常存储数据的地方,而访问这些数据就是通过上述所说的 LDAP。

LDAP 的基本模型

参考资料 openldap 基础知识

虽然名字听起来不像,但是本质上是一个数据库,只不过这个数据库稍微有点特殊。为了优化查询速度,放弃了诸如数据备份与恢复等功能;ldap 数据库一般用于记录员工相关信息;ldap 数据库条目的属性是固定的,不可以自定义等等。

如果大家搜索过 ldap 的相关知识就一定会看到这个图片。结合关键词表可以理解该图片表示一个叫做 babs 的人,是 example.com 公司 People 部门下的一个员工。虽然这个图片形象的解释了 ldap 组织数据的方式,但是至少我看了这个图片之后不明白 ldap 是怎么存储数据的。

其实也很简单,ldap 意思是简单目录访问协议,因此还是需要回到目录上。把图里面的节点想成一个一个的文件夹,大概是这个样子。

然后我们只需要把 babs 的属性,诸如邮箱、电话、密码等信息序列化成文件之后存放在 com/example/People/babs 文件夹下面就行了。

这个结构的可拓展性也很强,可以很方便的增加组织、成员、属性。

LDAP 的信息模型是建立在 "条目"(entries)的基础上。一个条目是一些属性的集合,并且具有一个全局唯一的 "可区分名称"(DN),一个条目可以通过 DN 来引用。每一个条目的属性具有一个类型和一个或者多个值。类型通常是容易记忆的名称,比如 "cn"是通用名称(common name) ,或者 "mail"是电子邮件地址。条目的值的语法取决于属性类型。比如,cn属性可能具有一个值 "Babs Jensen" 。一个 mail属性可能包含 bbs@kevin.com 。一个 jpegphoto属性可能包含一幅 JPEG(二进制)格式的图片。

LDAP 的 objectClass

LDAP通过属性objectClass来控制哪一个属性必须出现或允许出现在一个条目中,它的值决定了该条目必须遵守的模式 规则。可以理解为关系数据库的表结构。接下来会用到的objectClass有

objectClass含义
olcGlobal全局配置文件类型, 主要是cn=config.ldif 的配置项
top顶层的对象
organization组织,比如公司名称,顶层的对象
organizationalUnit重要, 一个目录节点,通常是group,或者部门这样的含义
inetOrgPerson重要, 我们真正的用户节点类型,person类型, 叶子节点
groupOfNames重要, 分组的group类型,标记一个group节点
olcModuleList配置模块的对象

目录树概念

1、目录树:在一个目录服务系统中,整个目录信息集可以表示为一个目录信息树,树中的每个节点是一个条目。

2、条目:每个条目就是一条记录,每个条目有自己的唯一可区别的名称(DN)。

3、对象类:与某个实体类型对应的一组属性,对象类是可以继承的,这样父类的必须属性也会被继承下来。

4、属性:描述条目的某个方面的信息,一个属性由一个属性类型和一个或多个属性值组成,属性有必须属性和非必须属性。

DC、UID、OU、CN、SN、DN、RDN

> DN(Distinguished Name):类别名称,绝对位置
> RDN(Relative Distinguished Name):相对辨别名,相对路径
> CN(Common Name) :名称
> OU(Organizational Unit Name):组织名称
> O (Organizational Unit ):组织
> DC(Domain Componet):域名地址

几种基本模型

下面介绍它的几种基本模型

信息模型:在 LDAP 中信息以树状方式组织,在树状信息中的基本数据单元是条目,而每个条目由属性构成,属性中存储有属性值;

命名模型:LDAP 中的命名模型,也即 LDAP 中的条目定位方式。在 LDAP 中每个条目均有自己的 DN。DN 是该条目在整个树中的唯一名称标识,如同文件系统中,带路径的文件名就是 DN。

功能模型

在 LDAP 中共有四类 10种操作:

  • 查询类操作,如搜索、 比较;
  • 更新类操作,如添加条目、删除条目、修改条目、修改条目名;
  • 认证类操作,如绑定、解绑定;
  • 其它操作,如放弃和扩展操作。

安全模型:LDAP 中的安全模型主要通过身份认证、安全通道和访问控制来实现。

信息组织

目录条目以层次型的树状结构来组织。反应地域和组织机构界限。

树可以根据互联网域名组织,允许使用 DNS 为目录服务定位。

最顶层即根乘坐 “基准DN(baseDN)”,如 dc=example,dc=como=example.org

OU(organization Unit)用来表示公司内部机构,如部门等,也可表示设备、人员等。

配置 LDAP 服务器

依旧使用 Docker 来配置服务

这里使用的镜像地址 osixia/phpldapadmin 这里的 Docker Compose 文件参考 Docker OpenLDAP + phpldapadmin example

创建 docker-compose.yml 注意,默认这玩意是没有 GUI 的,而且因为是在容器中,终端操作也不是很方便,所以这里最好搭配一个 phpLDAPadmin 来使用

version: '2'
services:
openldap:
image: osixia/openldap:1.2.3
container_name: openldap
environment:
LDAP_LOG_LEVEL: "256"
LDAP_ORGANISATION: "Example Inc."
LDAP_DOMAIN: "example.org"
LDAP_BASE_DN: ""
LDAP_ADMIN_PASSWORD: "admin"
LDAP_CONFIG_PASSWORD: "config"
LDAP_READONLY_USER: "false"
LDAP_READONLY_USER_USERNAME: "readonly"
LDAP_READONLY_USER_PASSWORD: "readonly"
LDAP_RFC2307BIS_SCHEMA: "false"
LDAP_BACKEND: "mdb"
LDAP_TLS: "true"
LDAP_TLS_CRT_FILENAME: "ldap.crt"
LDAP_TLS_KEY_FILENAME: "ldap.key"
LDAP_TLS_CA_CRT_FILENAME: "ca.crt"
LDAP_TLS_ENFORCE: "false"
LDAP_TLS_CIPHER_SUITE: "SECURE256:-VERS-SSL3.0"
LDAP_TLS_PROTOCOL_MIN: "3.1"
LDAP_TLS_VERIFY_CLIENT: "demand"
LDAP_REPLICATION: "false"
#LDAP_REPLICATION_CONFIG_SYNCPROV: "binddn="cn=admin,cn=config" bindmethod=simple credentials=$LDAP_CONFIG_PASSWORD searchbase="cn=config" type=refreshAndPersist retry="60 +" timeout=1 starttls=critical"
#LDAP_REPLICATION_DB_SYNCPROV: "binddn="cn=admin,$LDAP_BASE_DN" bindmethod=simple credentials=$LDAP_ADMIN_PASSWORD searchbase="$LDAP_BASE_DN" type=refreshAndPersist interval=00:00:00:10 retry="60 +" timeout=1 starttls=critical"
#docker-compose.ymlLDAP_REPLICATION_HOSTS: "#PYTHON2BASH:['ldap://ldap.example.org','ldap://ldap2.example.org']"
KEEP_EXISTING_CONFIG: "false"
LDAP_REMOVE_CONFIG_AFTER_SETUP: "true"
LDAP_SSL_HELPER_PREFIX: "ldap"
tty: true
stdin_open: true
volumes:
- ./ldap/lib:/var/lib/ldap
- ./ldap/etc:/etc/ldap/slapd.d
- ./assets/certs:/container/service/slapd/assets/certs/
ports:
- "389:389"
- "636:636"
domainname: "example.org" # important: same as hostname
hostname: "example.org"
phpldapadmin:
image: osixia/phpldapadmin:latest
container_name: phpldapadmin
environment:
PHPLDAPADMIN_LDAP_HOSTS: "openldap"
PHPLDAPADMIN_HTTPS: "false"
ports:
- "8389:80" # 设置端口
depends_on:
- openldap

启动

docker-compose up -d

访问

# 登陆
Username: "cn=admin,dc=example,dc=org"
Password: "admin"

http://localhost:8389/

然后就能访问了

在命令行里访问

首先打开 Docker 的终端

# 首先输入这个命令检查是否成功启动了
slaptest

然后尝试检索这个 LDAP 数据库

# ldapsearch = ldap + search 下面的命令也是一样的组合方式
ldapsearch -x -H ldap://localhost -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin

LDIF 文本条目格式

详情参考 OpenLDAP LDIF 详解

初始状态下,LDAP是一个空目录,即没有任何数据。可通过程序代码向目录数据库中添加数据,也可使用OpenLDAP客户端工具 ldapadd 命令来完成添加数据的操作,该命令可将一个 LDIF 文件中的条目添加到目录。因此,需要首先创建一个 LDIF 文件,然后再进行添加操作。

LDIF 文件特点

  • LDIF 文件每行的结尾不允许有空格或者制表符。
  • LDIF 文件允许相关属可以重复赋值并使用。
  • LDIF 文件以 .ldif 结尾命名。
  • LDIF 文件以以 # 号开头的一行为注释,可以作为解释使用。
  • LDIF 文件所有的赋值方式为:属性:[空格]属性值
  • LDIF 文件通过空行来定义一个条目,空格前一个条目,空格后为另一个条目的开始。

注:如果要手动定义 LDIF 文件添加修改条目,需要了解以上相关特点;否则,会提示各种各样的语法错误。而且 OpenLDAP 服务器中定义 LDIF 文件,每个条目必须包含一个 objectclass 属性,并且需要定义值,objectclass 属性又顶级之分,在定义 objectclass 之前需要了解 objectclass 的相关依赖性,否则在添加或者修改时也会显示相关语法错误。

LDIF 格式语法

LDIF 文件存取 OpenLDAP 条目标准格式:

# 注释,对用于条目进行解释
dn: 条目名称
objectclass (对象类): 属性值
objectclass (对象类): 属性值
...

LDIF 格式样例如下:

dn: uid=Guodayong,ou=people,dc=dgy,dc=com # DN 描述项,在整个目录树上为唯一的
objectclass: top
objectclass: posixAccount
objectclass: shadowAccount
objectclass: person
objectclass: inetOrgPerson
objectclass: hostObject
sn: Guo
cn: Guodayong
telephoneNameber: xxxxxxxxxxx
mail: dayong_guo@126.com

注:冒号后面有一个空格,然后才是属性的值,schema 规范定义要求很严格,(请切记!)

数据的增删改

参考资料 3. 数据的增删减改

ldapmodify、ldapadd 命令格式如下。

ldapmodify [ldap 服务器地址] [你的用户名] [你的密码] [ldif 文件的地址]

前三个选项很好理解,ldif 文件是用来干什么呢。大家应该注意到命令格式里面没有出现任何与数据相关的信息,既然是对数据库进行增删减改,怎么可能没有数据信息呢。对了,数据信息全部写在 ldif 文件里面。ldif 文件对书写格式有特殊的要求,但是只要你跟着写上一次,就学会了。

这个是 ldapmodify 常用选项表。

增删 inetOrgPerson 类

给 ldap 数据库增加信息可以用 ldapmodify 也可以用 ldapadd。

我们需要为 Barbara 创建一个条目,这个条目需要包含他的名字、姓、邮箱、uid、称号这些信息。

写成 ldif 文件就是这个样子。

dn: cn=Barbara,dc=example,dc=org
objectClass: inetOrgPerson
cn: Barbara
sn: Jensen
title: the world's most famous mythical manager
mail: bjensen@example.com
uid: bjensen

第一行是有关 dn 的信息,这里是把 barbara 归到了 example.org 下面。需要注意的是 dn 的冒号后面有一个空格,这个是必须的。

第二行本文中的所有信息存储在 inetOrgPerson 类的实例中,后面的就是 barbara 的邮箱、称号之类的信息了。

首先,进入到 docker 容器内部。将信息写入 ldif 文件。(注意,下图是在容器内部执行的,这里只是在外部演示一下这个写法)

补充两个小知识,上面这样写的意思:

1、这里的 cat 有啥用?

cat主要有三大功能:

  1. 一次显示整个文件。$ cat filename
  2. 从键盘创建一个文件。$ cat > filename
    只能创建新文件,不能编辑已有文件.
  3. 将几个文件合并为一个文件: $cat file1 file2 > file

2、<< EOF 是什么意思,它表示监听输入,当监听输入 EOF 时退出编辑

[root@localhost ~]# cat >  linuxsir.org.txt  << EOF   # 注:创建 linuxsir.org.txt 文件;
> 我来测试 cat 创建文件,并且为文件输入内容; # 注:这是为linuxsir.org.txt文件输入内容;
> 北南南北 测试; # 注:这是为linuxsir.org.txt文件输入内容;
> EOF # 注:退出编辑状态;

接下来分别演示 ldapadd 和 ldapmodify 的用法。

ldapadd 新增

ldapadd -x -H ldap://127.0.0.2:389 -D "cn=admin,dc=example,dc=org" -w admin -f barbara.ldif

接下来用 ldapsearch 来查看该数据是否被正确的写入。

ldapsearch -x -H ldap://127.0.0.2:389  -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin 

此时可以登陆 phpldapadmin 也能看到这个新增加的用户

和其他数据库一样,ldapsearch 可以增加查询条件。

ldapsearch -x -H ldap://127.0.0.2:389  -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin "cn=*,dc=example,dc=org"

ldapdelete 删除

为了之后的实验,我们需要把这个条目删掉。

ldapdelete -x -H ldap://127.0.0.2:389  -D "cn=admin,dc=example,dc=org" -w admin  "cn=Barbara,dc=example,dc=org"

ldapmodify 修改

ldapmodify 和 ldapadd 类似,但是需要对 ldif 文件进行小小的修改。 在 ldif 文件的第二行加上。这样 ldapmodify 才知道本 ldif 文件的目的是增加一个条目。

changetype: add
ldapmodify -x -H ldap://127.0.0.2:389 -D "cn=admin,dc=example,dc=org" -w admin -f barbara.ldif

添加组织

增加组织和增加人员类似。

dn: ou=People,dc=example,dc=org
objectclass: top
objectclass: organizationalUnit
ou: People

dn: ou=Servers,dc=example,dc=org
objectclass: top
objectclass: organizationalUnit
ou: Servers

更改条目的属性

更改条目的属性也分为好几种,比如更改现有的属性,增加新属性等。

更改现有属性

dn: cn=Barbara,dc=example,dc=org
changetype: modify
replace: title
title: one of the world's most famous mythical manager

新增一个属性

dn: cn=Barbara,dc=example,dc=org
changetype: modify
add: description
description: Barbara description

将Barbara移动到people组织下

dn: cn=Barbara,dc=example,dc=org
changetype: modify
newsuperior: ou=People,dc=example,dc=org

ldap 用户

上一节在 ldap 数据库中增加了一个新的成员 Barbara,但是没有为其设置密码。为其设置密码之后就可以以 barbara 这个用户的身份登录 ldap 了。

添加人员

ou 并不能当做分组,而仅仅是组织架构的一个单元。ldap 的分组都是通过单独的 group来实现的。

添加人员对应的是树的叶子节点,使用的 objectClass: inetOrgPerson

添加组织部门对应的是目录,使用的 objectClass: organizationalUnit

我们要把人员添加到 ou=People,dc=demo,dc=com

创建 adduser.ldif

dn: ou=研发部门,ou=People,dc=demo,dc=com
changetype: add
objectClass: organizationalUnit
ou: 研发部门

dn: ou=后台组,ou=研发部门,ou=People,dc=demo,dc=com
changetype: add
objectClass: organizationalUnit
ou: 后台组



dn: cn=ryan.miao,ou=后台组,ou=研发部门,ou=People,dc=demo,dc=com
changetype: add
objectClass: inetOrgPerson
cn: ryan.miao
departmentNumber: 1
sn: Miao
title: 大牛
mail: ryan.miao@demo.com
uid: 10000
displayName: 中文名



dn: cn=someone,ou=后台组,ou=研发部门,ou=People,dc=demo,dc=com
changetype: add
objectClass: inetOrgPerson
cn: someone
departmentNumber: 1
sn: someone
title: Java工程师
mail: someone@demo.com
uid: 10001
displayName: 某人


dn: ou=测试组,ou=研发部门,ou=People,dc=demo,dc=com
changetype: add
objectClass: organizationalUnit
ou: 测试组


dn: cn=tester.miao,ou=测试组,ou=研发部门,ou=People,dc=demo,dc=com
changetype: add
objectClass: inetOrgPerson
cn: tester.miao
departmentNumber: 2
sn: Miao
title: 测试工程师
mail: tester@demo.com
uid: 10002
displayName: 测试某人


dn: ou=HR,ou=People,dc=demo,dc=com
changetype: add
objectClass: organizationalUnit
ou: HR


dn: cn=fang.huang,ou=HR,ou=People,dc=demo,dc=com
changetype: add
objectClass: inetOrgPerson
cn: fang.huang
departmentNumber: 3
sn: Huang
title: HRBP
mail: fang.huang@demo.com
uid: 10003
displayName: 黄芳

使用 ldapadd 来添加我们的用户

[root@e6043aeb680e data]# ldapadd -x -D cn=admin,dc=demo,dc=com -w admin -f adduser.sh 
adding new entry "ou=研发部门,ou=People,dc=demo,dc=com"

adding new entry "ou=后台组,ou=研发部门,ou=People,dc=demo,dc=com"

adding new entry "cn=ryan.miao,ou=后台组,ou=研发部门,ou=People,dc=demo,dc=com"

adding new entry "cn=someone,ou=后台组,ou=研发部门,ou=People,dc=demo,dc=com"

adding new entry "ou=测试组,ou=研发部门,ou=People,dc=demo,dc=com"

adding new entry "cn=tester.miao,ou=测试组,ou=研发部门,ou=People,dc=demo,dc=com"

adding new entry "ou=HR,ou=People,dc=demo,dc=com"

adding new entry "cn=fang.huang,ou=HR,ou=People,dc=demo,dc=com"

使用ldapsearch来查询用户

指定唯一 id来查询某个用户,比如 cn唯一,则

[root@e6043aeb680e data]# ldapsearch -x -D cn=admin,dc=demo,dc=com -w admin -b "dc=demo,dc=com" "cn=ryan.miao"  

ldappasswd 重置密码

用于设置和更新密码的 ldap 命令有:

ldappasswd # 管理员使用,用于重置其他用户的密码
ldapmodify # 所有人员都可以使用,用于更改自己的密码

他的使用方式是是这样的。三部分,第一部分是 ldap 服务器的地址,第二部分是当前用户的 dn 和密码,最后一部分是需要设置密码的账户的 dn。

ldappasswd -x -H ldap://127.0.0.2:389 -D "cn=admin,dc=example,dc=org" -w admin "cn=Barbara,dc=example,dc=org"

至此我们就为 Barbara 设置好了密码。但是这个密码是随机生成的,不好记忆,更改的方式很简单,可以通过 ldappasswd 命令来修改。

命令的第一部分是 ldap 服务器的地址,第二部分是 Barbara 的 dn 和我们刚刚生成的随机密码,第三部分是新的密码。

ldappasswd -x -H ldap://127.0.0.2:389 -D "cn=Barbara,dc=example,dc=org" -w xxxx -s mima

ldapmodify 修改密码

ldapmodify 也可以用于更改密码,示例的 ldif 文件如下:

dn: cn=Barbara Jensen,dc=example,dc=org
changetype: modify
replace: userPassword
userPassword: xinmima

修改

ldapmodify -a -H ldap://127.0.0.2:389-D "cn=Barbara,dc=example,dc=org" -w mima -f modifybarbara.ldif